﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;
using MugenMvvmToolkit.Interfaces.ViewModels;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.ViewModels;
using MugenMvvmToolkitTest.TestInfrastructure;
using Should;

namespace MugenMvvmToolkitTest.ViewModel
{
    [TestClass]
    public partial class ViewModelBaseTest : TestBase
    {
        #region Nested types

        public class ViewModelBaseWithContext : ViewModelBase, IViewModel
        {
            #region Fields

            private List<Action> _actions = new List<Action>();

            #endregion

            #region Properties

            public IInitializationContext Context { get; private set; }

            #endregion

            #region Implementation of IViewModel

            /// <summary>
            ///     Initializes the current view model with specified <see cref="IInitializationContext" />.
            /// </summary>
            /// <param name="context">
            ///     The specified <see cref="IInitializationContext" />.
            /// </param>
            public void InitializeViewModel(IInitializationContext context)
            {
                Context = context;
                foreach (Action action in _actions)
                {
                    action();
                }
                _actions = null;
            }

            /// <summary>
            ///     Invokes an action after the current view model was initialized.
            ///     Provides safe access to the current view model, if you are using asynchronous initialization.
            /// </summary>
            /// <param name="action">The specified delegate to invoke.</param>
            public void InvokeAfterInitialization(Action<IViewModel> action)
            {
                if (_actions == null)
                {
                    action(this);
                    return;
                }
                _actions.Add(() => action(this));
            }

            #endregion
        }

        public class TestViewModelBase : ViewModelBase
        {
        }

        public class ViewModelBaseWithDisplayName : ViewModelBase, IHasDisplayName
        {
            #region Implementation of IHasDisplayName

            /// <summary>
            ///     Gets or sets the display name for the current model.
            /// </summary>
            public string DisplayName { get; set; }

            #endregion
        }

        #endregion

        #region Methods

        [TestMethod]
        public void GetViewModelImplicitPolicyShouldUseDefaultPolicy()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase = (ViewModelBaseWithContext)viewModel.GetViewModel(typeof(ViewModelBaseWithContext));
            viewModelBase.Context.ActivationPolicy.ShouldEqual(GlobalSettings.DefaultActivationPolicy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void GetViewModelGenericImplicitPolicyShouldUseDefaultPolicy()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase = viewModel.GetViewModel<ViewModelBaseWithContext>();
            viewModelBase.Context.ActivationPolicy.ShouldEqual(GlobalSettings.DefaultActivationPolicy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }


        [TestMethod]
        public void GetViewModelExplicitPolicyShouldUseSpecifiedPolicy()
        {
            IActivationPolicy policy = ActivationPolicy.WithView;
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase =
                (ViewModelBaseWithContext)viewModel.GetViewModel(typeof(ViewModelBaseWithContext), policy);
            viewModelBase.Context.ActivationPolicy.ShouldEqual(policy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void GetViewModelGenericExplicitPolicyShouldUseSpecifiedPolicy()
        {
            IActivationPolicy policy = ActivationPolicy.WithView;
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase = viewModel.GetViewModel<ViewModelBaseWithContext>(policy);
            viewModelBase.Context.ActivationPolicy.ShouldEqual(policy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }


        [TestMethod]
        public void GetViewModelThroughConstructorImplicitPolicyShouldUseDefaultPolicy()
        {
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase =
                (ViewModelBaseWithContext)
                    viewModel.GetViewModel(getViewModel: adapter => new ViewModelBaseWithContext());
            viewModelBase.Context.ActivationPolicy.ShouldEqual(GlobalSettings.DefaultActivationPolicy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void GetViewModelThroughConstructorGenericImplicitPolicyShouldUseDefaultPolicy()
        {
            ViewModelBase viewModel = GetViewModelBase();
            ViewModelBaseWithContext viewModelBase = viewModel.GetViewModel(adapter => new ViewModelBaseWithContext());
            viewModelBase.Context.ActivationPolicy.ShouldEqual(GlobalSettings.DefaultActivationPolicy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }


        [TestMethod]
        public void GetViewModelThroughConstructorExplicitPolicyShouldUseSpecifiedPolicy()
        {
            IActivationPolicy policy = ActivationPolicy.WithView;
            ViewModelBase viewModel = GetViewModelBase();
            var viewModelBase =
                (ViewModelBaseWithContext)
                    viewModel.GetViewModel(getViewModel: adapter => new ViewModelBaseWithContext(), policy: policy);
            viewModelBase.Context.ActivationPolicy.ShouldEqual(policy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void GetViewModelThroughConstructorGenericExplicitPolicyShouldUseSpecifiedPolicy()
        {
            IActivationPolicy policy = ActivationPolicy.WithView;
            ViewModelBase viewModel = GetViewModelBase();
            ViewModelBaseWithContext viewModelBase = viewModel.GetViewModel(adapter => new ViewModelBaseWithContext(),
                policy);
            viewModelBase.Context.ActivationPolicy.ShouldEqual(policy);
            viewModelBase.Context.ViewModel.ShouldEqual(viewModelBase);
            viewModelBase.Context.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void OnInitializedViewModelPropertyIsInitializedShouldChanged()
        {
            var testViewModel = new TestViewModelBase();
            testViewModel.IsInitialized.ShouldBeFalse();
            ViewModelProvider.InitializeViewModel(testViewModel, GlobalSettings.DefaultActivationPolicy, null);
            testViewModel.IsInitialized.ShouldBeTrue();
        }

        [TestMethod]
        public void ReInitializationShouldThrowException()
        {
            ThreadManager.ImmediateInvokeAsync = true;
            ViewModelBase viewModel = GetViewModelBase();
            ShouldThrow<InvalidOperationException>(() => ViewModelProvider.InitializeViewModel(viewModel, GlobalSettings.DefaultActivationPolicy, null));
        }

        [TestMethod]
        public void DefaultPropertiesShouldBeInitialized()
        {
            ThreadManager.ImmediateInvokeAsync = true;
            Settings.WithoutClone = true;
            ViewModelBase viewModel = GetViewModelBase();

            var testViewModel = viewModel.GetViewModel<TestViewModelBase>();
            testViewModel.ThreadManager.ShouldEqual(ThreadManager);
            testViewModel.IocAdapter.Parent.ShouldEqual(viewModel.IocAdapter);
            testViewModel.Settings.ShouldEqual(Settings);
        }

        [TestMethod]
        public void DisplayNameShouldBeInitializedIfProviderCanBeResolved()
        {
            const string name = "name";
            ThreadManager.ImmediateInvokeAsync = true;
            Settings.WithoutClone = true;
            DisplayNameProvider.GetNameDelegate = info => name;
            ViewModelBase viewModel = GetViewModelBase();

            var testViewModel = viewModel.GetViewModel<ViewModelBaseWithDisplayName>();
            testViewModel.DisplayName.ShouldEqual(name);
        }

        [TestMethod]
        public void DisplayNameShouldNotBeInitializedIfProviderCannotBeResolved()
        {
            ThreadManager.ImmediateInvokeAsync = true;
            CanBeResolvedTypes.Remove(typeof(IDisplayNameProvider));
            ViewModelBase viewModel = GetViewModelBase();

            var testViewModel = viewModel.GetViewModel<ViewModelBaseWithDisplayName>();
            testViewModel.DisplayName.ShouldBeNull();
        }

        [TestMethod]
        public void InvokeAfterInitializationShouldInvokeAfterViewModelWasInitialized()
        {
            var testViewModel = new TestViewModelBase();
            testViewModel.IsInitialized.ShouldBeFalse();
            bool isInvoked = false;
            Action invokeAction = () => isInvoked = true;
            testViewModel.InvokeAfterInitialization(@base => invokeAction());
            isInvoked.ShouldBeFalse();

            ViewModelProvider.InitializeViewModel(testViewModel, GlobalSettings.DefaultActivationPolicy, null);
            testViewModel.IsInitialized.ShouldBeTrue();
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void InvokeAfterInitializationShouldInvokeimmediatlyIfViewModelIsInitialized()
        {
            ThreadManager.ImmediateInvokeAsync = false;
            ViewModelBase viewModel = GetViewModelBase();

            var testViewModel = viewModel.GetViewModel<TestViewModelBase>();
            bool isInvoked = false;
            Action invokeAction = () => isInvoked = true;
            testViewModel.InvokeAfterInitialization(@base => invokeAction());
            isInvoked.ShouldBeTrue();
        }

        [TestMethod]
        public void ViewShouldBeInitializedIfActivationPolicyWithView()
        {
            ViewModelBase viewModel = GetViewModelBase();
            IInitializationContext initializationContext = null;
            ViewManager.InitializeViewDelegate = context =>
                                                 {
                                                     initializationContext = context;
                                                     return true;
                                                 };

            var policy = ActivationPolicy.WithView;
            var testViewModel = viewModel.GetViewModel<TestViewModelBase>(policy);
            initializationContext.ShouldNotBeNull();
            initializationContext.ActivationPolicy.ShouldEqual(policy);
            initializationContext.ViewModel.ShouldEqual(testViewModel);
            initializationContext.ParentViewModel.ShouldEqual(viewModel);
        }

        [TestMethod]
        public void ViewShouldNotBeInitializedIfActivationPolicyWithoutView()
        {
            ViewModelBase viewModel = GetViewModelBase();
            IInitializationContext initializationContext = null;
            ViewManager.InitializeViewDelegate = context =>
                                                 {
                                                     initializationContext = context;
                                                     return true;
                                                 };

            var policy = ActivationPolicy.WithoutViewUseParentIocAdaper;
            var testViewModel = viewModel.GetViewModel<TestViewModelBase>(policy);
            initializationContext.ShouldBeNull();
        }

        #endregion

        #region Properties

        protected Func<ViewModelBase> GetViewModelBaseDelegate { get; set; }

        #endregion

        #region Methods

        private ViewModelBase GetViewModelBase()
        {
            ViewModelBase viewModel = GetViewModelBaseDelegate();
            InitializeViewModel(viewModel, IocAdapter, GlobalSettings.DefaultActivationPolicy);
            return viewModel;
        }

        #endregion

        #region Overrides of TestBase

        protected override void OnInit()
        {
            GetViewModelBaseDelegate = () => new TestViewModelBase();
        }

        #endregion
    }
}